"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
exports.__esModule = true;
exports.getOriginInfo = exports.proxyFetch = exports.proxyXmlHttp = exports.proxyHash = exports.proxyHistory = exports.wrHistory = exports.afterLoad = void 0;
// TODO UV
// TODO User Agent 解析
// TODO IP 采集解析
var store_1 = require("./store");
var behaviorStore_1 = require("./behaviorStore");
var history = window.history;
var afterLoad = function (callback) {
    if (document.readyState === 'complete') {
        setTimeout(callback);
    }
    else {
        window.addEventListener('pageshow', callback, {
            once: true,
            capture: true
        });
    }
};
exports.afterLoad = afterLoad;
// 派发出新的 Event
// 这个好像用到了高阶函数, 牛蛙
var wr = function (type) {
    var orig = history[type];
    return function () {
        var rv = orig.apply(this, arguments);
        var e = new Event(type);
        window.dispatchEvent(e);
        return rv;
    };
};
// 添加 pushState replaceState 事件
var wrHistory = function () {
    history.pushState = wr('pushState');
    history.replaceState = wr('replaceState');
};
exports.wrHistory = wrHistory;
// 为 pushState 以及 replaceState 方法添加 Event 事件
var proxyHistory = function (handler) {
    // 添加对 replaceState 的监听
    window.addEventListener('replaceState', function (e) { return handler(e); }, true);
    // 添加对 pushState 的监听
    window.addEventListener('pushState', function (e) { return handler(e); }, true);
};
exports.proxyHistory = proxyHistory;
var proxyHash = function (handler) {
    // 添加对 hashchange 的监听
    // hash 变化除了触发 hashchange ,也会触发 popstate 事件,而且会先触发 popstate 事件，我们可以统一监听 popstate
    // 这里可以考虑是否需要监听 hashchange,或者只监听 hashchange
    window.addEventListener('hashchange', function (e) { return handler(e); }, true);
    // 添加对 popstate 的监听
    // 浏览器回退、前进行为触发的 可以自己判断是否要添加监听
    window.addEventListener('popstate', function (e) { return handler(e); }, true);
};
exports.proxyHash = proxyHash;
// 调用 proxyXmlHttp 即可完成全局监听 XMLHttpRequest
var proxyXmlHttp = function (sendHandler, loadHandler) {
    if ('XMLHttpRequest' in window &&
        typeof window.XMLHttpRequest === 'function') {
        var oXMLHttpRequest_1 = window.XMLHttpRequest;
        if (!window.oXMLHttpRequest) {
            // oXMLHttpRequest 为原生的 XMLHttpRequest，可以用以 SDK 进行数据上报，区分业务
            window.oXMLHttpRequest = oXMLHttpRequest_1;
        }
        window.XMLHttpRequest = function () {
            // 覆写 window.XMLHttpRequest
            var xhr = new oXMLHttpRequest_1();
            var open = xhr.open, send = xhr.send;
            var metrics = {};
            xhr.open = function (method, url) {
                metrics.method = method;
                metrics.url = url;
                open.call(xhr, method, url, true);
            };
            xhr.send = function (body) {
                metrics.body = body || '';
                metrics.requestTime = new Date().getTime();
                // sendHandler 可以在发送 Ajax 请求之前，挂载一些信息，比如 header 请求头
                // setRequestHeader 设置请求header，用来传输关键参数等
                // xhr.setRequestHeader('xxx-id', 'VQVE-QEBQ');
                if (typeof sendHandler === 'function')
                    sendHandler(xhr);
                send.call(xhr, body);
            };
            xhr.addEventListener('loadend', function () {
                var status = xhr.status, statusText = xhr.statusText, response = xhr.response;
                metrics = __assign(__assign({}, metrics), { status: status, statusText: statusText, response: response, responseTime: new Date().getTime() });
                if (typeof loadHandler === 'function')
                    loadHandler(metrics);
                // xhr.status 状态码
            });
            return xhr;
        };
    }
};
exports.proxyXmlHttp = proxyXmlHttp;
// 调用 proxyFetch 即可完成全局监听 fetch
var proxyFetch = function (sendHandler, loadHandler) {
    if ('fetch' in window && typeof window.fetch === 'function') {
        var oFetch_1 = window.fetch;
        if (!window.oFetch) {
            window.oFetch = oFetch_1;
        }
        window.fetch = function (input, init) { return __awaiter(void 0, void 0, void 0, function () {
            var metrics;
            return __generator(this, function (_a) {
                // init 是用户手动传入的 fetch 请求互数据，包括了 method、body、headers，要做统一拦截数据修改，直接改init即可
                if (typeof sendHandler === 'function')
                    sendHandler(init);
                metrics = {};
                metrics.method = (init === null || init === void 0 ? void 0 : init.method) || '';
                metrics.url =
                    (input && typeof input !== 'string' ? input === null || input === void 0 ? void 0 : input.url : input) || ''; // 请求的url
                metrics.body = (init === null || init === void 0 ? void 0 : init.body) || '';
                metrics.requestTime = new Date().getTime();
                return [2 /*return*/, oFetch_1.call(window, input, init).then(function (response) { return __awaiter(void 0, void 0, void 0, function () {
                        var res, _a;
                        var _b;
                        return __generator(this, function (_c) {
                            switch (_c.label) {
                                case 0:
                                    res = response.clone();
                                    _a = [__assign({}, metrics)];
                                    _b = { status: res.status, statusText: res.statusText };
                                    return [4 /*yield*/, res.text()];
                                case 1:
                                    metrics = __assign.apply(void 0, _a.concat([(_b.response = _c.sent(), _b.responseTime = new Date().getTime(), _b)]));
                                    if (typeof loadHandler === 'function')
                                        loadHandler(metrics);
                                    return [2 /*return*/, response];
                            }
                        });
                    }); })];
            });
        }); };
    }
};
exports.proxyFetch = proxyFetch;
// 返回 OI 用户来路信息
var getOriginInfo = function () {
    var _a;
    return {
        referrer: document.referrer,
        type: ((_a = window.performance) === null || _a === void 0 ? void 0 : _a.navigation.type) || ''
    };
};
exports.getOriginInfo = getOriginInfo;
var UserVitals = /** @class */ (function () {
    function UserVitals(engineInstance) {
        var _this = this;
        // 封装用户行为的上报入口
        this.userSendHandler = function (data) {
            // 进行通知内核实例进行上报;
        };
        // 获取 PI 页面基本信息
        this.getPageInfo = function () {
            var _a = window.location, host = _a.host, hostname = _a.hostname, href = _a.href, protocol = _a.protocol, origin = _a.origin, port = _a.port, pathname = _a.pathname, search = _a.search, hash = _a.hash;
            var _b = window.screen, width = _b.width, height = _b.height;
            var language = navigator.language, userAgent = navigator.userAgent;
            return {
                host: host,
                hostname: hostname,
                href: href,
                protocol: protocol,
                origin: origin,
                port: port,
                pathname: pathname,
                search: search,
                hash: hash,
                title: document.title,
                language: language.substr(0, 2),
                userAgent: userAgent,
                winScreen: "".concat(width, "x").concat(height),
                docScreen: "".concat(document.documentElement.clientWidth || document.body.clientWidth, "x").concat(document.documentElement.clientHeight || document.body.clientHeight)
            };
        };
        // 补齐 pathname 和 timestamp 参数
        this.getExtends = function () {
            return {
                page: _this.getPageInfo().pathname,
                timestamp: new Date().getTime()
            };
        };
        // 初始化用户自定义埋点数据的获取上报
        this.initCustomerHandler = function () {
            var handler = function (options) {
                // 记录到 UserMetricsStore
                _this.metrics.add(store_1.metricsNameUserVital.CDR, options);
                // 自定义埋点的信息一般立即上报
                _this.userSendHandler(options);
                // 记录到用户行为记录栈
                _this.breadcrumbs.push(__assign({ category: store_1.metricsNameUserVital.CDR, data: options }, _this.getExtends()));
            };
            return handler;
        };
        // 初始化 PI 页面基本信息的获取以及返回
        this.initPageInfo = function () {
            var info = _this.getPageInfo();
            var metrics = info;
            _this.metrics.set(store_1.metricsNameUserVital.PI, metrics);
        };
        // 初始化 RCR 路由跳转的获取以及返回
        this.initRouteChange = function () {
            var handler = function (e) {
                // 正常记录
                var metrics = {
                    // 跳转的方法 eg:replaceState
                    jumpType: e.type,
                    // 创建时间
                    timestamp: new Date().getTime(),
                    // 页面信息
                    pageInfo: _this.getPageInfo()
                };
                // 一般路由跳转的信息不会进行上报，根据业务形态决定；
                _this.metrics.add(store_1.metricsNameUserVital.RCR, metrics);
                // 行为记录 不需要携带 pageInfo
                delete metrics.pageInfo;
                // 记录到行为记录追踪
                var behavior = __assign({ category: store_1.metricsNameUserVital.RCR, data: metrics }, _this.getExtends());
                _this.breadcrumbs.push(behavior);
            };
            (0, exports.proxyHash)(handler);
            // 为 pushState 以及 replaceState 方法添加 Evetn 事件
            (0, exports.proxyHistory)(handler);
        };
        // 初始化 PV 的获取以及返回
        this.initPV = function () {
            var handler = function () {
                var metrics = {
                    // 还有一些标识用户身份的信息，由项目使用方传入，任意拓展 eg:userId
                    // 创建时间
                    timestamp: new Date().getTime(),
                    // 页面信息
                    pageInfo: _this.getPageInfo(),
                    // 用户来路
                    originInformation: (0, exports.getOriginInfo)()
                };
                _this.userSendHandler(metrics);
                // 一般来说， PV 可以立即上报
            };
            (0, exports.afterLoad)(function () {
                handler();
            });
            (0, exports.proxyHash)(handler);
            // 为 pushState 以及 replaceState 方法添加 Evetn 事件
            (0, exports.proxyHistory)(handler);
        };
        // 初始化 OI 用户来路的获取以及返回
        this.initOriginInfo = function () {
            var info = (0, exports.getOriginInfo)();
            var metrics = info;
            _this.metrics.set(store_1.metricsNameUserVital.OI, metrics);
        };
        // 初始化 CBR 点击事件的获取和返回
        this.initClickHandler = function (mountList) {
            var handler = function (e) {
                var _a, _b;
                // 这里是根据 tagName 进行是否需要捕获事件的依据，可以根据自己的需要，额外判断id\class等
                // 先判断浏览器支持 e.path ，从 path 里先取
                var target = (_a = e.path) === null || _a === void 0 ? void 0 : _a.find(function (x) { var _a; return mountList.includes((_a = x.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase()); });
                // 不支持 path 就再判断 target
                target =
                    target ||
                        (mountList.includes((_b = e.target.tagName) === null || _b === void 0 ? void 0 : _b.toLowerCase())
                            ? e.target
                            : undefined);
                if (!target)
                    return;
                var metrics = {
                    tagInfo: {
                        id: target.id,
                        classList: Array.from(target.classList),
                        tagName: target.tagName,
                        text: target.textContent
                    },
                    // 创建时间
                    timestamp: new Date().getTime(),
                    // 页面信息
                    pageInfo: _this.getPageInfo()
                };
                // 除开商城业务外，一般不会特意上报点击行为的数据，都是作为辅助检查错误的数据存在;
                _this.metrics.add(store_1.metricsNameUserVital.CBR, metrics);
                // 行为记录 不需要携带 完整的pageInfo
                delete metrics.pageInfo;
                // 记录到行为记录追踪
                var behavior = __assign({ category: store_1.metricsNameUserVital.CBR, data: metrics }, _this.getExtends());
                _this.breadcrumbs.push(behavior);
            };
            window.addEventListener('click', function (e) {
                handler(e);
            }, true);
        };
        // 初始化 http 请求的数据获取和上报
        this.initHttpHandler = function () {
            var loadHandler = function (metrics) {
                if (metrics.status < 400) {
                    // 对于正常请求的 HTTP 请求来说,不需要记录 请求体 和 响应体
                    delete metrics.response;
                    delete metrics.body;
                }
                // 记录到 UserMetricsStore
                _this.metrics.add(store_1.metricsNameUserVital.HT, metrics);
                // 记录到用户行为记录栈
                _this.breadcrumbs.push(__assign({ category: store_1.metricsNameUserVital.HT, data: metrics }, _this.getExtends()));
            };
            (0, exports.proxyXmlHttp)(null, loadHandler);
            (0, exports.proxyFetch)(null, loadHandler);
        };
        this.engineInstance = engineInstance;
        this.metrics = new store_1["default"]();
        // 限制最大行为追踪记录数为 100，真实场景下需要外部传入自定义;
        this.maxBehaviorRecords = 100;
        // 初始化行为追踪记录
        this.breadcrumbs = new behaviorStore_1["default"]({
            maxBehaviorRecords: this.maxBehaviorRecords
        });
        // 初始化 用户自定义 事件捕获
        this.customHandler = this.initCustomerHandler();
        // 作为 真实sdk 的时候，需要在初始化时传入与默认值合并;
        this.clickMountList = ['button'].map(function (x) { return x.toLowerCase(); });
        // 重写事件
        (0, exports.wrHistory)();
        // 初始化页面基本信息
        this.initPageInfo();
        // 初始化路由跳转获取
        this.initRouteChange();
        // 初始化用户来路信息获取
        this.initOriginInfo();
        // 初始化 PV 的获取;
        this.initPV();
        // 初始化 click 事件捕获
        this.initClickHandler(this.clickMountList);
        // 初始化 Http 请求事件捕获
        this.initHttpHandler();
        // 上报策略在后几篇细说
    }
    return UserVitals;
}());
exports["default"] = UserVitals;
